package util

import (
	"encoding/json"
	"fmt"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclparse"
	"github.com/ihaiker/tfwrapper/internal/errors"
	"github.com/zclconf/go-cty/cty"
	ctyjson "github.com/zclconf/go-cty/cty/json"
	"golang.org/x/crypto/ssh/terminal"
	"os"
	"strconv"
)

var parser = hclparse.NewParser()
var writer = getDiagnosticsWriter(parser)

func ParseHcl(filename string) (file *hcl.File, err hcl.Diagnostics) {
	defer errors.Recover(func(cause error) {
		err = hcl.Diagnostics{
			&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Parse hcl file",
				Detail:   fmt.Sprintf("Parse hcl file %s: %s", filename, cause.Error()),
			},
		}
	})
	return parser.ParseHCLFile(filename)
}

// GetDiagnosticsWriter returns a hcl2 parsing diagnostics emitter for the current terminal.
func getDiagnosticsWriter(parser *hclparse.Parser) hcl.DiagnosticWriter {
	termColor := terminal.IsTerminal(int(os.Stderr.Fd()))
	termWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
	if err != nil {
		termWidth = 80
	}
	return hcl.NewDiagnosticTextWriter(os.Stderr, parser.Files(), uint(termWidth), termColor)
}

func WriteDiagnostics(diagnostics hcl.Diagnostics) {
	_ = writer.WriteDiagnostics(diagnostics)
}

func HasErrors(diags hcl.Diagnostics) bool {
	has := diags.HasErrors()
	if !has && len(diags) > 0 {
		WriteDiagnostics(diags)
		diags = hcl.Diagnostics{}
	}
	return has
}

type ctyJsonValue struct {
	Value json.RawMessage
	Type  interface{}
}

func CtyValueToVarValue(value cty.Value) string {
	jsonBytes, _ := ctyjson.Marshal(value, cty.DynamicPseudoType)
	j := new(ctyJsonValue)
	_ = json.Unmarshal(jsonBytes, j)

	if str, err := strconv.Unquote(string(j.Value)); err == nil {
		return str
	} else {
		return string(j.Value)
	}
}

// TerraformOutputJsonToCtyValueMap takes the terraform output json and converts to a mapping between output keys to the
// parsed cty.Value encoding of the json objects.
func TerraformOutputJsonToCtyValueMap(moduleName string, jsonBytes []byte) (map[string]cty.Value, error) {
	type OutputMeta struct {
		Sensitive bool            `json:"sensitive"`
		Type      json.RawMessage `json:"type"`
		Value     json.RawMessage `json:"value"`
	}
	var outputs map[string]OutputMeta
	err := json.Unmarshal(jsonBytes, &outputs)
	if err != nil {
		return nil, errors.OutputParsingError{Module: moduleName, Err: err}
	}
	flattenedOutput := map[string]cty.Value{}
	for k, v := range outputs {
		outputType, err := ctyjson.UnmarshalType(v.Type)
		if err != nil {
			return nil, errors.OutputParsingError{Module: moduleName, Err: err}
		}
		outputVal, err := ctyjson.Unmarshal(v.Value, outputType)
		if err != nil {
			return nil, errors.OutputParsingError{Module: moduleName, Err: err}
		}
		flattenedOutput[k] = outputVal
	}
	return flattenedOutput, nil
}
